﻿#region ... Copyright Notice ...
/*
   Copyright 2008 Tyler Jensen

	Author: Tyler Jensen    http://www.tsjensen.com/blog

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
#endregion

using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Web;
using System.Xml.Linq;
using Atrax.Contracts;
using Atrax.Library;
using Atrax.Consumers;
using Atrax.Security;
using Atrax.Logging;
using Atrax.Utils;

namespace Atrax.Spider
{
	internal class RequestHandler
	{
		SpiderRequest request;
		string spiderQueryId;
		string pacingHost;
		double requiredSecondsBetweenRequests;
		bool pacingRequired;
		string ipAddress = null;

		internal RequestHandler(SpiderRequest request, string spiderQueryId, string pacingHost, double requiredSecondsBetweenRequests, bool pacingRequired)
		{
			this.request = request;
			this.spiderQueryId = spiderQueryId;
			this.pacingHost = pacingHost;
			this.requiredSecondsBetweenRequests = requiredSecondsBetweenRequests;
			this.pacingRequired = pacingRequired;
		}

		internal string SpiderQueryId { get { return spiderQueryId; } }
		internal string PacingHost { get { return pacingHost; } }
		internal double RequiredSecondsBetweenRequests { get { return requiredSecondsBetweenRequests; } }
		internal bool PacingRequired { get { return pacingRequired; } }
		internal SpiderRequest Request { get { return request; } }

		//called by managed thread pool to start processing when there is a thread available
		internal void ThreadPoolCallback(object threadContext)
		{
			try
			{
				//get an IP (loop and sleep until an IP is obtained) from workerQueue to allow lock of collection

				CheckoutToken token = SpiderController.GetIpAddressCheckoutToken(pacingHost);
				ipAddress = SpiderController.CheckoutIpAddress(pacingHost, token);
				int attempts = 0;
				while (ipAddress == null && attempts < 1000)  //1000 tries before giving up
				{
					Thread.Sleep(900); //wait 9/10 of one second before trying again
					ipAddress = SpiderController.CheckoutIpAddress(pacingHost, token);
					attempts++;
				}

				SpiderResult result = new SpiderResult() 
				{ 
					AuthenticationToken = this.request.AuthenticationToken, 
					QueryId = this.request.QueryId, 
					SpiderQueryId = this.spiderQueryId 
				};

				if (ipAddress != null)
				{
					//normal http request
					int readTimeoutSeconds = 180;
					int maxBytes = 2097152;  //2mb limit
					int maxRedirects = 5;
					bool followRedirOffHost = false;
					//process request and create response from that					  
					try
					{
						HttpClient http = new HttpClient(ipAddress, readTimeoutSeconds, maxBytes, maxRedirects, followRedirOffHost);
						result.Response = http.ProcessRequest(this.request.Request);
					}
					catch (Exception e)
					{
						result.Response = new InternetResponse() 
						{ 
							Request = this.request.Request, 
							StatusCode = "502", 
							StatusDescription = e.Message, 
							SpiderQueryId = this.spiderQueryId 
						};
					}
				}
				else
				{
					//deal with failure to obtain IP address by sending back failure notice to caller as response
					result.Response = new InternetResponse()
					{
						Request = this.request.Request,
						StatusCode = "503",
						StatusDescription = "Failed to obtain an IP address.",
						SpiderQueryId = this.spiderQueryId
					};
				}

				MasterClient mc = null;
				try
				{
					//send callback 
					mc = MasterClientFactory.Create();
					mc.ReportResult(result);
					mc.Close();
				}
				catch (Exception ce)
				{
					if (mc != null) mc.Abort();
					Log.App(LogEvents.SpiderCallbackException, ce);
				}
			}
			catch (Exception unhandledException)
			{
				Log.App(LogEvents.SpiderQueryUnhandled, unhandledException);
			}
			finally
			{
				//remove from collection
				SpiderController.CompleteJob(this.spiderQueryId, pacingHost, ipAddress);
			}
			
			//done, thread goes back to pool
		}
	}
}
